package com.zhenmyan.secdeal.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zhenmyan.secdeal.Enum.ResponseEnum;
import com.zhenmyan.secdeal.pojo.Order;
import com.zhenmyan.secdeal.pojo.SecDealMessage;
import com.zhenmyan.secdeal.pojo.SecDealOrder;
import com.zhenmyan.secdeal.pojo.User;
import com.zhenmyan.secdeal.rabbitmq.MQSender;
import com.zhenmyan.secdeal.service.GoodsService;
import com.zhenmyan.secdeal.service.OrderService;
import com.zhenmyan.secdeal.service.SecDealOrderService;
import com.zhenmyan.secdeal.utils.JsonUtil;
import com.zhenmyan.secdeal.vo.GoodsVo;
import com.zhenmyan.secdeal.vo.Response;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * description:
 *
 * @author 颜真明
 * @date 2023/5/11  8:47
 */
@Controller
@RequestMapping("/secDeal")
public class SecDealController implements InitializingBean {

    @Resource
    private GoodsService goodsService;

    @Resource
    private SecDealOrderService secDealOrderService;

    @Resource
    private OrderService orderService;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private MQSender mqSender;

    @Resource
    private RedisScript<Long> stockScript;

    private Map<Long, Boolean> emptyStockMap;


    /**
     * 采用模版引擎
     *
     * @param model
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping("/doSecDealV1")
    public String doSecDealV1(Model model, User user, Long goodsId) {
        if (user == null) {
            return "login";
        }
        model.addAttribute("user", user);
        GoodsVo goodsVo = goodsService.getGoodsVoById(goodsId);
        // 判断库存
        if (goodsVo.getStockCount() < 1) {
            model.addAttribute("errmsg", ResponseEnum.EMPTY_STOCK.getMessage());
            return "secDealFail";
        }
        // 判断是否重复抢购
        SecDealOrder secDealOrder = secDealOrderService
                .getOne(new QueryWrapper<SecDealOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
        if (secDealOrder != null) {
            model.addAttribute("errmsg", ResponseEnum.REPEAT_ERROR.getMessage());
            return "secDealFail";
        }
        Order order = orderService.secDeal(user, goodsVo);
        model.addAttribute("order", order);
        model.addAttribute("goods", goodsVo);
        return "orderDetail";

    }

    /**
     * 采用动静分离进行优化
     *
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping(value = "/doSecDealV2", method = RequestMethod.POST )
    @ResponseBody
    public Response doSecDealV2(User user, Long goodsId) {
        if (user == null) {
            return Response.error(ResponseEnum.SESSION_ERROR);
        }
        GoodsVo goodsVo = goodsService.getGoodsVoById(goodsId);
        // 判断库存
        if (goodsVo.getStockCount() < 1) {
            return Response.error(ResponseEnum.EMPTY_STOCK);
        }
        // 判断是否重复抢购
        SecDealOrder secDealOrder = secDealOrderService
                .getOne(new QueryWrapper<SecDealOrder>().eq("user_id", user.getId()).eq("goods_id", goodsId));
        if (secDealOrder != null) {
            return Response.error(ResponseEnum.REPEAT_ERROR);
        }
        Order order = orderService.secDeal(user, goodsVo);
        return Response.success(order);

    }

    /**
     * ***** 秒杀过程
     *
     *      lua脚本：判断库存是否足够，足够则减1，不够返回-1
     *      发送mq
     *
     *
     * @param user
     * @param goodsId
     * @return
     */
    @RequestMapping(value = "/{path}/doSecDeal", method = RequestMethod.POST )
    @ResponseBody
    public Response doSecDeal(@PathVariable String path, User user, Long goodsId) {
        if (user == null) {
            return Response.error(ResponseEnum.SESSION_ERROR);
        }
        // 利用内存缓存，避免频繁访问redis
        if (emptyStockMap.get(goodsId)) {
            return Response.error(ResponseEnum.EMPTY_STOCK);
        }
        // 判断path是否正确
        //优化后代码
        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return Response.error(ResponseEnum.REQUEST_ILLEGAL);
        }

        // 判断是否重复抢购
        // 预减库存，原子性操作
//        Long stock = redisTemplate.opsForValue().decrement("secDealGoods:"+goodsId);
        Long stock =  (Long)redisTemplate.execute(stockScript, Collections.singletonList("secDealGoods:"+goodsId), Collections.EMPTY_LIST);
        if (stock < 0) {
            emptyStockMap.put(goodsId, true);
            redisTemplate.opsForValue().increment("secDealGoods:"+goodsId);
            return Response.error(ResponseEnum.EMPTY_STOCK);
        }
        SecDealMessage secDealMessage = new SecDealMessage(user, goodsId);
        mqSender.secDealSend(JsonUtil.object2JsonStr(secDealMessage));
        return Response.success(ResponseEnum.SUCCESS);
    }

    /**
     * 秒杀接口，给前端进行轮询
     *
     * 如果异步操作已经生成订单，则给前端返回结果
     *
     * @param user
     * @param goodsId
     * @return
     */
    @ApiOperation("获取秒杀结果")
    @GetMapping("/getResult")
    @ResponseBody
    public Response getResult(User user, Long goodsId) {
        if (user == null) {
            return Response.error(ResponseEnum.SESSION_ERROR);
        }
        Long orderId = secDealOrderService.getResult(user, goodsId);
        return Response.success(orderId);
    }

    @ApiOperation("获取秒杀地址")
    @GetMapping("/path")
    @ResponseBody
    public Response path(User user, Long goodsId, HttpServletRequest request) {
        if (user == null) {
            return Response.error(ResponseEnum.SESSION_ERROR);
        }

        ValueOperations valueOperations = redisTemplate.opsForValue();


        // 计数器限流：限制访问次数，5秒内访问5次
        String uri = request.getRequestURI();
        Integer count = (Integer) valueOperations.get(uri + ":" + user.getId());
        if (count == null) {
            valueOperations.set(uri + ":" + user.getId(), 1, 5, TimeUnit.SECONDS);
        } else if (count < 5) {
            valueOperations.increment(uri + ":" + user.getId());
        } else {
            return Response.error(ResponseEnum.ACCESS_LIMIT_REACHED);
        }

        String str = orderService.createPath(user, goodsId);
        return Response.success(str);
    }

    /**
     *  ***** 库存预热
     *
     *        实现InitializingBean的afterPropertiesSet()方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        emptyStockMap = new HashMap<>();
        List<GoodsVo> goodsVoList = goodsService.getGoodsVo();
        if (CollectionUtils.isEmpty(goodsVoList)) {
            return;
        }
        goodsVoList.forEach(goodsVo -> {
            redisTemplate.opsForValue().set("secDealGoods:"+goodsVo.getId(), goodsVo.getStockCount());
            emptyStockMap.put(goodsVo.getId(), false);
        });

    }
}
